<template>
  <div id="flowWrap" ref="flowWrap" class="flow-wrap" @drop="dropNode($event)" @dragover="allowDrop($event)"
    @click="handleNoActiveItem($event)">
    <div id="flowWrap-content">
      <div v-show="auxiliaryLine.isShowXLine" class="auxiliary-line-x"
        :style="{width: auxiliaryLinePos.width, top:auxiliaryLinePos.y + 'px', left: auxiliaryLinePos.offsetX + 'px'}">
      </div>
      <div v-show="auxiliaryLine.isShowYLine" class="auxiliary-line-y"
        :style="{height: auxiliaryLinePos.height, left:auxiliaryLinePos.x + 'px', top: auxiliaryLinePos.offsetY + 'px'}">
      </div>
      <flow-panel-node v-for="node in nodeList" :key="node.id" :id="node.id" :node="node"
        :isActive="currentItem?(currentItem.id === node.id):false" @deleteNode="deleteNode"
        @changeLineState="changeLineState" @setNode="handleSetNode"></flow-panel-node>
    </div>
    <el-dialog v-model="dataModal" title="节点配置" custom-class="node-config-dialog" width="60%"
      :before-close="dataBeforeClose" destroy-on-close>
      <node-config :node="configNodeInfo"></node-config>
    </el-dialog>
  </div>
</template>
<script>
import {
  computed,
  defineAsyncComponent,
  defineComponent,
  nextTick,
  onMounted,
  provide,
  ref,
  watch,
} from "vue";
import { jsPlumb } from "jsplumb";
import {
  jsplumbConnectOptions,
  jsplumbSetting,
  jsplumbSourceOptions,
  jsplumbTargetOptions,
} from "./config";
import { UUID3 } from "../../../../utils/utils";
import { debounce } from "lodash-es";
import panzoom from "panzoom";
import { detailETLById } from "../../../../service/data_etl";
import { useRoute } from "vue-router";
import { useEtlStore } from "../../../../store/flowEtl";
import { etlType, etl_libs } from "../../../../utils/constant";

let jsplumb = null;
const NodeConfig = defineAsyncComponent(() =>
  import("./node-config/index.vue")
);
const FlowPanelNode = defineAsyncComponent(() =>
  import("./flow-panel-node.vue")
);
let pan = null;

export default defineComponent({
  name: "flow-panel-content",
  components: {
    FlowPanelNode,
    NodeConfig,
  },
  setup() {
    const route = useRoute();
    const dataModal = ref(false);
    const etlStore = useEtlStore();
    const data_etl_id = computed(() => etlStore.data_etl_id);
    const nodeList = computed(() => etlStore.nodeList);
    const lineList = computed(() => etlStore.lineList);
    const currentItem = computed(() => etlStore.currentItem);
    const dragItem = computed(() => etlStore.dragItem);
    const data_etl = computed(() => etlStore.data_etl);
    const flowWrap = ref();
    const auxiliaryLine = ref({
      isShowXLine: false,
      isShowYLine: false,
    });
    const configNodeInfo = ref({});
    const commonGrid = ref([5, 5]);
    const auxiliaryLinePos = ref({
      width: "100%",
      height: "100%",
      offsetX: 0,
      offsetY: 0,
      x: 20,
      y: 20,
    });
    const flowInit = () => {
      jsplumb.ready(() => {
        jsplumb.deleteEveryConnection();
        jsplumb.deleteEveryEndpoint();
        jsplumb.importDefaults(jsplumbSetting);
        jsplumb.bind("beforeDrop", (info) => {
          // 1 输入 input 不能作为 target
          const sourceInfo = nodeList.value.find((item) => {
            return item.id === info.sourceId;
          });
          //此处可以添加是否创建连接的校验， 返回 false 则不添加；
          if (sourceInfo.type === "output") {
            ElMessage({
              type: "warning",
              message: "输入数据集禁止做为目标源...",
            });
            return false;
          }
          if (!sourceInfo.status) {
            ElMessage({
              type: "warning",
              message: "请先配置源目标！",
            });
            return false;
          }
          let targetInfo = nodeList.value.find((item) => {
            return item.id === info.targetId;
          });
          if (targetInfo.type === "input") {
            ElMessage({
              type: "warning",
              message: "输出数据集禁止做为输入源",
            });
            return false;
          }
          if (targetInfo.type === "join") {
            let joinSource = lineList.value.filter((item) => {
              return item.to === targetInfo.id;
            });
            if (joinSource.length >= 2) {
              ElMessage({
                type: "warning",
                message: "只能对两个节点进行关联操作",
              });
              return false;
            }
          }
          if (etlType.includes(targetInfo.type)) {
            let currentItemNum = lineList.value.filter((item) => {
              return item.to === targetInfo.id;
            });
            if (currentItemNum.length >= 1) {
              ElMessage({
                type: "warning",
                message: "只能对一个节点进行操作",
              });
              return false;
            }
            let nodes = [...nodeList.value].map((v) => {
              if (info.targetId === v.id) {
                v.originFields = sourceInfo.config.fields;
                v.config.fields = sourceInfo.config.fields;
              }
              if (v.type === "output") {
                v.status = true;
              }
              return v;
            });
            etlStore.setNodeList(nodes);
          }
          return true;
        });
        // 连线创建成功后，维护本地数据
        jsplumb.bind("connection", (evt) => {
          addLine(evt);
        });
        //连线双击删除事件
        jsplumb.bind("dblclick", (conn, originalEvent) => {
          confirmDelLine(conn);
        });
        //断开连线后，维护本地数据
        jsplumb.bind("connectionDetached", (evt) => {
          deleteLine(evt);
        });
        setTimeout(() => {
          loadEasyFlow();
        }, 500);
        // 会使整个jsPlumb立即重绘。
        jsplumb.setSuspendDrawing(false, true);
      });
      initPanZoom();
    };
    const addLine = (line) => {
      let from = line.source.id;
      let to = line.target.id;
      etlStore.addNode({
        from: from,
        to: to,
        label: "连线名称",
        id: UUID3(8),
        Remark: "",
      });
    };
    const confirmDelLine = (line) => {
      if (data_etl.value.status === 1) {
        return;
      }
      ElMessageBox.confirm(`确认删除该连线？`, "删除连线", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        let nodes = [...nodeList.value].map((item) => {
          if (item.id === line.targetId) {
            item.status = false;
            item.originFields = [];
            etl_libs.forEach((lib) => {
              lib.children.forEach((childLib) => {
                if (childLib.type === item.type) {
                  item.config = childLib.config;
                }
              });
            });
          }
          return item;
        });
        etlStore.setNodeList(nodes);
        jsplumb.deleteConnection(line);
      });
    };
    const loadEasyFlow = () => {
      for (let i = 0; i < nodeList.value.length; i++) {
        // 设置源点，可以拖出线连接其他节点
        let node = nodeList.value[i];
        jsplumb.makeSource(node.id, jsplumbSourceOptions);
        // // 设置目标点，其他源点拖出的线可以连接该节点
        jsplumb.makeTarget(node.id, jsplumbTargetOptions);
        draggableNode(node.id);
      }

      // 初始化连线
      jsplumb.unbind("connection");
      for (let i = 0; i < lineList.value.length; i++) {
        let line = lineList.value[i];
        jsplumb.connect(
          {
            source: line.from,
            target: line.to,
          },
          jsplumbConnectOptions
        );
      }
      jsplumb.bind("connection", (evt) => {
        let from = evt.source.id;
        let to = evt.target.id;
        etlStore.addLine({
          from: from,
          to: to,
          label: "连线名称",
          id: UUID3(8),
          Remark: "",
        });
      });
    };
    // 加载流程图
    const draggableNode = (nodeId) => {
      jsplumb.draggable(nodeId, {
        // grid: commonGrid.value,
        drag: (params) => {
          alignForLine(nodeId, params.pos);
        },
        start: () => {},
        stop: (params) => {
          auxiliaryLine.value.isShowXLine = false;
          auxiliaryLine.value.isShowYLine = false;
          debounce(changeNodePosition(nodeId, params.pos), 500);
        },
      });
    };
    //移动节点时，动态显示对齐线
    const alignForLine = (nodeId, position) => {
      let showXLine = false,
        showYLine = false;
      nodeList.value.some((el) => {
        if (el.id !== nodeId && el.left == position[0] + "px") {
          auxiliaryLinePos.value.x = position[0] + 60;
          showYLine = true;
        }
        if (el.id !== nodeId && el.top == position[1] + "px") {
          auxiliaryLinePos.value.y = position[1] + 20;
          showXLine = true;
        }
      });
      auxiliaryLine.value.isShowYLine = showYLine;
      auxiliaryLine.value.isShowXLine = showXLine;
    };
    const changeNodePosition = (nodeId, pos) => {
      let nodes = [...nodeList.value].map((v) => {
        if (nodeId === v.id) {
          v.left = pos[0] + "px";
          v.top = pos[1] + "px";
        }
        return v;
      });
      etlStore.setNodeList(nodes);
    };

    const deleteLine = (line) => {
      lineList.value.forEach((item, index) => {
        if (item.from === line.sourceId && item.to === line.targetId) {
          etlStore.reduceLine(index);
        }
      });
    };
    // dragover默认事件就是不触发drag事件，取消默认事件后，才会触发drag事件
    const allowDrop = (event) => {
      event.preventDefault();
      event.stopPropagation();
    };
    const getScale = () => {
      let scale1;
      if (jsplumb.pan) {
        const { scale } = jsplumb.pan.getTransform();
        scale1 = scale;
      } else {
        const matrix = window.getComputedStyle(
          jsplumb.getContainer()
        ).transform;
        scale1 = matrix.split(", ")[3] * 1;
      }
      jsplumb.setZoom(scale1);
      return scale1;
    };
    // 添加新的节点
    const addNode = (temp) => {
      etlStore.addNode(temp);
      etlStore.setCurrentItem(temp);
      nextTick(() => {
        jsplumb.makeSource(temp.id, jsplumbSourceOptions);
        jsplumb.makeTarget(temp.id, jsplumbTargetOptions);
        draggableNode(temp.id);
        etlStore.setDragItem({});
      });
    };
    //删除节点
    const deleteNode = (node) => {
      ElMessageBox.confirm(`你确定删除当前节点么？`, "刪除节点", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        if (currentItem.value && currentItem.value.id === node.id) {
          etlStore.setCurrentItem({ id: null });
        }
        nodeList.value.some((v, index) => {
          if (v.id === node.id) {
            etlStore.reduceNode(index);
            jsplumb.remove(v.id);
            return true;
          } else {
            return false;
          }
        });
      });
    };
    //更改连线状态
    const changeLineState = (nodeId, val) => {
      if (!nodeId) {
        return;
      }
      let activeNode = nodeList.value.find((item) => {
        return item.id === nodeId;
      });
      etlStore.setCurrentItem(activeNode);
      let lines = jsplumb.getAllConnections();
      lines.forEach((line) => {
        if (line.targetId === nodeId || line.sourceId === nodeId) {
          if (val) {
            line.canvas.classList.add("active");
          }
        }
      });
    };
    const initPanZoom = () => {
      const mainContainer = jsplumb.getContainer();
      const mainContainerWrap = mainContainer.parentNode;
      pan = panzoom(mainContainer, {
        smoothScroll: false,
        bounds: true,
        zoomDoubleClickSpeed: 1,
        minZoom: 0.5,
        maxZoom: 2,
        //设置滚动缩放的组合键，默认不需要组合键
        beforeWheel: (e) => {
          return dataModal.value;
          // let shouldIgnore = !e.ctrlKey
          // return shouldIgnore
        },
        beforeMouseDown: function (e) {
          return dataModal.value;
        },
      });
      jsplumb.mainContainerWrap = mainContainerWrap;
      jsplumb.pan = pan;
      // 缩放时设置jsPlumb的缩放比率
      pan.on("zoom", (e) => {
        const { x, y, scale } = e.getTransform();
        jsplumb.setZoom(scale);
        //根据缩放比例，缩放对齐辅助线长度和位置
        auxiliaryLinePos.value.width = (1 / scale) * 100 + "%";
        auxiliaryLinePos.value.height = (1 / scale) * 100 + "%";
        auxiliaryLinePos.value.offsetX = -(x / scale);
        auxiliaryLinePos.value.offsetY = -(y / scale);
      });
      pan.on("panend", (e) => {
        const { x, y, scale } = e.getTransform();
        auxiliaryLinePos.value.width = (1 / scale) * 100 + "%";
        auxiliaryLinePos.value.height = (1 / scale) * 100 + "%";
        auxiliaryLinePos.value.offsetX = -(x / scale);
        auxiliaryLinePos.value.offsetY = -(y / scale);
      });

      // 平移时设置鼠标样式
      mainContainerWrap.style.cursor = "grab";
      mainContainerWrap.addEventListener("mousedown", function wrapMousedown() {
        this.style.cursor = "grabbing";
        mainContainerWrap.addEventListener("mouseout", function wrapMouseout() {
          this.style.cursor = "grab";
        });
      });
      mainContainerWrap.addEventListener("mouseup", function wrapMouseup() {
        this.style.cursor = "grab";
      });
    };
    const fixNodesPosition = () => {
      let nodes = [...nodeList.value];
      //初始化节点位置  （以便对齐,居中）
      if (nodes && flowWrap) {
        const nodeWidth = 120;
        const nodeHeight = 40;
        let wrapInfo = document
          .querySelector("#flowWrap")
          .getBoundingClientRect();
        let maxLeft = 0,
          minLeft = wrapInfo.width,
          maxTop = 0,
          minTop = wrapInfo.height;
        let nodePoint = {
          left: 0,
          right: 0,
          top: 0,
          bottom: 0,
        };
        let fixTop = 0,
          fixLeft = 0;
        nodes.forEach((el) => {
          let top = Number(el.top.substring(0, el.top.length - 2));
          let left = Number(el.left.substring(0, el.left.length - 2));
          maxLeft = left > maxLeft ? left : maxLeft;
          minLeft = left < minLeft ? left : minLeft;
          maxTop = top > maxTop ? top : maxTop;
          minTop = top < minTop ? top : minTop;
        });
        nodePoint.left = minLeft;
        nodePoint.right = wrapInfo.width - maxLeft - nodeWidth;
        nodePoint.top = minTop;
        nodePoint.bottom = wrapInfo.height - maxTop - nodeHeight;

        fixTop =
          nodePoint.top !== nodePoint.bottom
            ? (nodePoint.bottom - nodePoint.top) / 2
            : 0;
        fixLeft =
          nodePoint.left !== nodePoint.right
            ? (nodePoint.right - nodePoint.left) / 2
            : 0;

        nodes.map((el) => {
          let top = Number(el.top.substring(0, el.top.length - 2)) + fixTop;
          let left = Number(el.left.substring(0, el.left.length - 2)) + fixLeft;
          el.top = Math.round(top / 20) * 20 + "px";
          el.left = Math.round(left / 20) * 20 + "px";
        });
        debounce(() => {
          etlStore.setNodeList(nodes);
        }, 800);
      }
    };
    const dropNode = (event) => {
      if (!dragItem.value.type) {
        return;
      }
      const containerRect = jsplumb.getContainer().getBoundingClientRect();
      const scale = getScale();
      let left = (event.pageX - containerRect.left - 60) / scale;
      let top = (event.pageY - containerRect.top - 20) / scale;
      addNode({
        ...dragItem.value,
        id: UUID3(8),
        top: Math.round(top / 20) * 20 + "px",
        left: Math.round(left / 20) * 20 + "px",
      });
    };
    const handleNoActiveItem = (e) => {
      // 阻止向父组件冒泡
      e.stopPropagation();
      e.preventDefault();
    };
    const handleSetNode = (node) => {
      nextTick(() => {
        configNodeInfo.value = node;
        dataModal.value = true;
        pan.pause();
      });
    };
    const getETLById = () => {
      if (data_etl_id.value) {
        detailETLById({ data_etl_id: data_etl_id.value }).then((res) => {
          etlStore.setFlowData(JSON.parse(res.data.content));
          etlStore.setDataEtl(res.data);
          fixNodesPosition();
          nextTick(() => {
            flowInit();
          });
        });
      }
    };
    const dataBeforeClose = () => {
      pan.resume();
      dataModal.value = false;
    };
    const nodeConfigClose = () => {
      pan.resume();
      dataModal.value = false;
    };
    watch(
      () => route.query.id,
      (val) => {
        etlStore.setDataEtlId(Number(val));
        getETLById();
      }
    );
    provide("nodeConfigClose", nodeConfigClose);
    onMounted(() => {
      jsplumb = jsPlumb.getInstance();
      etlStore.setDataEtlId(Number(route.query.id));
      getETLById();
    });
    return {
      flowWrap,
      auxiliaryLine,
      commonGrid,
      auxiliaryLinePos,
      nodeList,
      lineList,
      dragItem,
      currentItem,
      dataModal,
      data_etl_id,
      data_etl,
      configNodeInfo,
      jsplumbSetting,
      jsplumbConnectOptions,
      jsplumbSourceOptions,
      jsplumbTargetOptions,
      fixNodesPosition,
      flowInit,
      dropNode,
      allowDrop,
      deleteNode,
      changeLineState,
      handleNoActiveItem,
      getETLById,
      handleSetNode,
      nodeConfigClose,
      dataBeforeClose,
      etlType,
    };
  },
});
</script>
<style lang="less" scoped>
.flow-wrap {
  height: 100%;
  position: relative;
  overflow: hidden;
  outline: none !important;
  flex-grow: 1;
  background-image: url("https://fengnull.gitee.io/picture_house/etl/point.png");

  #flowWrap-content {
    position: relative;
    width: 100%;
    height: 100%;

    .auxiliary-line-x {
      position: absolute;
      border: 0.5px dashed #2ab1e8;
      z-index: 9999;
    }

    .auxiliary-line-y {
      position: absolute;
      border: 0.5px dashed #2ab1e8;
      z-index: 9999;
    }
  }
}
</style>
<style lang="less">
.node-config-dialog {
  .el-dialog__body {
    padding-bottom: 0;
  }
}
</style>